在實作 ORM 之前,我們可能要從 Migration 開始認識,Rails 有一個相當聰明的機制,可以方便管理資料庫的結構,這也是許多人剛接觸(像是我)一直會卡關的地方,總是不了解 migration 到底在做什麼,簡單的來說他就是在紀錄每一次資料庫變更的過程,不管是 新增一個資料表
,或是 刪除一個欄位
,搭配版本控管工具,可以確保在開發過程中,與其他一起開發的同事,資料庫長的一樣
我們就先從建立一個 Migration 來熟悉一下,接下來我們會用 sqlite3
這個套件來實作,sqlite3
也是 Rails 預設安裝好的套件,用這個套件可以方便我們跟 SQLite
做串接,至於什麼是 SQLite
?想像它是一個輕型的資料庫,不用複雜的設定就可以直接使用,而且他還支援 SQL
語法
一樣先在 Mavericks 安裝需要的 gem
# Mavericks/mavericks.gemspec
# .
# .
# (略)
spec.add_runtime_dependency "rack"
spec.add_runtime_dependency "erubi"
spec.add_runtime_dependency "multi_json"
# 加入這行
spec.add_runtime_dependency 'sqlite3'
安裝完後回到 just_do
,讓我們先來建立第一個 Table
# just_do/mini_migration.rb
require 'sqlite3'
conn = SQLite3::Database.new "just_do.db"
conn.execute <<SQL
create table tasks(
id INTEGER PRIMARY KEY,
title TEXT,
content TEXT);
SQL
這裡我們用手動的方式建立了一個 Migration 檔案,就如同一開頭所說的,migration 是用來描述資料庫的狀態,我們在 Migration 裡面先 new 了一個新的 Database,叫 just_do.db
,接著加上create table
SQL 語法,然後我們來執行這段程式碼
$ bundle exec ruby mini_migration.rb
執行完後會在 just_do
的根目錄底下看到 just_do.db
的檔案,也就是 SQLite
的資料庫,其實跟我們之前練習用 JSON
來示範基本的 Model 很像,都是對本機的檔案做存取寫入,差別在於 SQLite
是一個小型的資料庫系統
建立完後,我們可以寫一個 method 來查看 table
的 schema
,至於為什麼要查看 schema?有一個很重要的原因是 我們想要取得資料庫的欄位,透過這些欄位來建立物件的屬性
,之後的實作部分會用到
回到 Mavericks,我們現在要做一個 SQLite
的 Model,所以要建立一個 sqlite_model.rb
的檔案(跟前幾天的 file_model.rb
有點類似)
# mavericks/lib/mavericks/sqlite_model.rb
require 'sqlite3'
require 'mavericks/support'
DB = SQLite3::Database.new 'just_do.db'
module Mavericks
module Model
class SQLite
def self.table
Mavericks.to_underscore name
end
def self.schema
return @schema if @schema
@schema = {}
DB.table_info(table) do |row|
@schema[row["name"]] = row["type"]
end
@schema
end
end
end
end
我們現在還沒辦法讓開發者自己選擇資料庫,之後會再來實作這部分,這裡就先寫死在 Mavericks 裡面,接著寫一個 class method 來取得 table name
,依照 Rails 的做法,基本上是一個 Model,也就是一個 Class 會對應到資料庫的一個表格,所以 class name
其實就等於table_name
,只是我們需要借助 to_underscore
,來轉換大小寫和底線
至於 schema
這個 class method,就是將欄位資訊轉換成 Hash 回傳,然後我們來寫一個簡單的 Ruby 程式碼來看看呼叫 shcnema
# just_do/sqlite_test.rb
require 'sqlite3'
require 'mavericks/sqlite_model'
class Tasks < Mavericks::Model::SQLite
end
puts Tasks.schema
最後執行
$ bundle exec ruby sqlite_test.rb
# {"id"=>"INTEGER", "title"=>"TEXT", "content"=>"TEXT"}
成功取得專案目前的 schema
細心的你可能會發現一個問題,我們一般表格是用複數,Model 名稱會用單數,例如像上面的範例中,應該是命名為 class Task 才對?當然你拿掉 s
以後,會發現撈到 {}
,你可能會好奇為什麼?Rails 不是都可以嗎?
那是因為 Rails 用 ActiveSupport::Inflector
來幫我們處理這件事情,我們也可以仿照 Rails 來處理單複數的問題,不過英文的單複數的處理規則比我們想像的還要複雜多,更何況還有許多的例外情況,所以這裡我採用最基本的規則來處理,有興趣的人可以再加上其他條件
複數規則
1.無特殊情形:加 s
例如: book -> books 書
2.字尾是 s/x/z/sh/ch:加 es
例如: box ➝ boxes 箱子
接著我們用正規表示法來檢查條件,程式碼如下
# mavericks/lib/mavericks/support.rb
module Mavericks
# .
# .
# (略)
def self.to_plural(string)
pattern = /.*s$|x$|z$|sh$|ch$/
pattern.match?(string) ? "#{string}es" : "#{string}s"
end
end
我們加了一個 method 叫 to_plural
來處理複數轉換
接著在取得 table name 那裡也需要修正一下
def self.table
table_name = Mavericks.to_underscore name
# 多加了複數轉換
Mavericks.to_plural table_name
end
最後回到 sqlite_test.rb
把 s
拿掉測試看看結果
# just_do/sqlite_test.rb
require 'sqlite3'
require 'mavericks/sqlite_model'
class Task < Mavericks::Model::SQLite
end
puts Task.schema
最後執行
$ bundle exec ruby sqlite_test.rb
# {"id"=>"INTEGER", "title"=>"TEXT", "content"=>"TEXT"}
成功了!
我們有了第一個 migration,也學習 migration 的用處,也取得了 schema,利用 to_plural
也巧妙轉換了 Model 名稱 和 Table 名稱單複數的關係,看起來明天就可以進行更深入的實作了